<?php

namespace app\service;
use app\exception\ParameterException;
use app\exception\UserException;
use app\model\Member;
use app\model\MemberVip;
use app\model\UserMoney;
use app\utils\Jwt;
use app\utils\UserMoneyEmun;
use think\facade\Cache;
use think\facade\Db;
use app\model\Percentage;
class MemberService
{
    private $member;
    private $loginService;

    protected static $tempDiscountData = [];

    public static $memberVipId=[];

    private $userMoney;

    private $percentage;

    public function __construct()
    {
        $this->loginService = new LoginService('api_login_attempts:','api_login_lockout:');
        $this->member = new Member;
        $this->userMoney = new UserMoney;
        $this->percentage = new Percentage;
    }

    public function list($params)
    {
        $page = $params['page'] ?? 1;
        $pageSize = $params['pageSize'] ?? 10;

        $where = [];

        if(!empty($params['username'])) $where[] = ['username', 'like', '%'.trim($params['username']).'%'];

        if(isset($params['status']) && $params['status']!='' ) $where[]=['status','=',$params['status']];

        if(!empty($params['vip'])) $where[] = ['vip_id','=',$params['vip']];

        $result = $this->member->with(['vip','manager'])->withCount(['junior'])->where($where)->order(['updated_at'=>'desc','id' => 'desc'])->paginate([
            'list_rows'=> $pageSize,
            'page' => $page
        ])->hidden(['password'])->append(['sex_text','status_text']);

        return $result;
    }

    public function delete($id)
    {
        return $this->member->destroy($id);
    }

    public function update($id,$param)
    {
        $member = $this->member->find($id);
        //数据库存在的字段才更新
        return $member->allowField(['phone','avatar','sex','nickname','status','vip_id','alipay_account','card','realname'])->save($param);
    }

    public function login($param){
        // 提取用户名和密码
        $username = $param['username'] ?? '';
        $password = $param['password'] ?? '';

        // 检查是否超过登录尝试次数限制
        if (!$this->loginService->checkLoginAttempts($username)) {
            $remainingTime = $this->loginService->getLockoutTime($username);
            throw new UserException('登录失败次数过多，请' . ceil($remainingTime / 60) . '分钟后再试');
        }

        // 检查用户名和密码是否为空
        if (empty($username) || empty($password)) throw new UserException( '登录失败');

        // 查询用户是否存在
        $member = $this->member->where('username', $username)->find();

        // 用户不存在或密码不匹配
        if (!$member || !password_verify($password, $member->password)) {
            $this->loginService->incrementLoginAttempts($username);

            // 获取剩余尝试次数
            $remainingAttempts = $this->loginService->getRemainingAttempts($username);

            if ($remainingAttempts > 0) {
                throw new UserException('用户名或密码错误，您还有' . $remainingAttempts . '次尝试机会');
            } else {
                throw new UserException('登录失败次数过多，请10分钟后再试');
            }
        }

        // 检查用户状态是否正常
        if($member->status!=1) throw new UserException( '账号异常,请联系管理员!');

        // 登录成功，清除失败记录
        $this->loginService->clearLoginAttempts($username);

        // 获取JWT配置参数
        $secret = env('jwt.api_secret','');
        $expire = env('jwt.api_expires',0);

        // 生成JWT令牌
        $token = Jwt::generateToken($member->id,$member->invitation_code, $member->username, $secret, $expire);

        // 使用文件缓存，将令牌与用户ID关联并设置过期时间
        Cache::store('file')->set($token, $token, $expire);

        // 返回生成的JWT令牌和用户信息
        return [
            "token"=>$token,
            "username" => $member->username,
            "phone"=> $member->phone,
            "avatar" => $member->avatar,
            "sex" => $member->sex,
            "nickname"=> $member->nickname
        ];
    }

    public function logout($token)
    {
        return Cache::store('file')->delete($token);
    }

    /**
     * 更新用户余额并创建钱包记录
     *
     * 此函数用于根据给定的参数更新用户的余额，并创建相应的钱包记录以跟踪余额变动
     * 它首先检查用户是否有足够的余额进行负向调整，然后开始一个数据库事务以更新余额
     * 和创建钱包记录如果过程中遇到任何问题，事务将被回滚以保持数据一致性
     *
     * @param int $id 用户ID，用于识别需要更新余额的用户
     * @param array $param 包含更新信息的数组，包括价格调整和其他相关信息
     * @return bool 如果余额更新和钱包记录创建成功，则返回true；否则抛出异常
     * @throws ParameterException 如果用户余额不足或数据库操作失败，则抛出此异常
     */
    public function updateMoney($id,$param)
    {
        // 获取当前操作管理员的ID
        $admin_id  = request()->_uid;

        // 加载指定ID的用户信息，并对其进行排他性锁定以防止并发修改
        $member = $this->member->lock(true)->where('id', $id)->find();

        // 提取参数中的价格变动值，如果没有提供则默认为0
        $price = $param['price'] ?? 0;

        // 检查用户余额是否足够进行负向调整
        if($price < 0 && $member->money < abs($price))throw new ParameterException("用户余额不足");

        // 开始数据库事务
        Db::startTrans();

        try {
            // 更新用户余额
            $member->money = sprintf("%.2f", $member->money+$price);
            $member->save();

            // 创建钱包记录，记录用户的余额变动详情
            $this->userMoney->create([
                'member_id' => $member->id,
                'price' => $price,
                'type' => $param['type']??UserMoneyEmun::USER_MONEY_TYPE_ADJUST,
                'remark' => $param['remark'],
                'admin_id' => $admin_id,
                'balance' => $member->money
            ]);

            // 提交事务
            Db::commit();

            // 返回操作成功
            return true;
        }catch (\Exception $e){
            // 回滚事务以撤销所有更改
            Db::rollback();

            // 抛出异常以报告错误
            throw new ParameterException($e->getMessage());
        }
    }

    /**
     * 保存用户信息
     *
     * 该方法主要用于保存用户信息，包括用户注册和更新信息时的处理它首先对用户的密码进行加密，
     * 然后根据提供的手机号码生成用户名，如果提供了邀请码，则验证邀请码并关联上级用户最后，生成新的邀请码，
     * 并将用户信息保存到数据库中
     *
     * @param array $param 包含用户信息的数组，可能包括密码、用户名、手机号和邀请码
     * @return mixed 保存成功后的结果
     * @throws ParameterException 如果邀请码不存在或参数错误，则抛出异常
     */
    public function save($param){

        $code = $param['code']??'';
        //判断验证码
        if(!(new CodeService())->checkCode($param['phone'],$code)) throw new ParameterException('验证码错误');
        //对密码进行加密处理，确保安全性
        $param['password'] = password_hash($param['password'],PASSWORD_DEFAULT);
        //截取手机号码后四位
        $param['username'] = $param['phone'];
        $param['nickname'] = $param['username']?? "手机用户".substr($param['phone'], -4);
        $invitationCode = $param['invitation_code']??'';
        $param['status'] = 1;
        //判断邀请码是否存在
        if(!empty($invitationCode)){
            //根据邀请码查找上级用户
            $member = $this->member->where('invitation_code',$invitationCode)->find();
            //如果邀请码错误，抛出异常
            if(!$member) throw new ParameterException('邀请码错误');
            //将找到的上级用户的ID保存到参数中
            $param['pid'] = $member->id;
        }
        
        // else{
        //     $param['pid'] = $param['pid']??0;
        // }

        //生成新的邀请码
        $param['invitation_code'] = $this->generateInvitationCode();
        //允许指定字段并保存用户信息到数据库
        return $this->member->allowField(['username', 'password', 'phone','status','nickname','invitation_code','pid'])->create($param);
    }

    /**
     * 生成邀请码
     *
     * 此函数的目的是创建一个唯一的邀请码，确保它在系统中是唯一的
     * 它通过调用getRandomCode函数生成一个随机码，然后检查数据库中是否已有相同的邀请码存在
     * 如果存在重复，将重新调用自身生成新的邀请码，直到找到一个唯一的邀请码为止
     *
     * @return string 唯一的邀请码
     */
    public function generateInvitationCode(){
        // 生成一个6位的随机码
        $code = getRandomCode(6);
        // 检查数据库中是否已有相同的邀请码
        $member = $this->member->where('invitation_code',$code)->find();
        // 如果找到重复的邀请码，则重新生成
        if($member) return $this->generateInvitationCode();

        // 返回生成的唯一邀请码
        return $code;
    }

    public function forgetPassword($param)
    {
        try {
            $member_id  = request()->_mid;
            $code = $param['code']??'';
            $password = $param['password'];
            //判断验证码
            if(!(new CodeService())->checkCode($param['phone'],$code)) throw new ParameterException('验证码错误');

            //查看数据库
            $member = $this->member->where('phone',$param['phone'])->find();

            if(!$member) throw new ParameterException('用户不存在');

            if(password_verify($password, $member->password)) throw new ParameterException('密码与旧密码相同');

            //对密码进行加密处理，确保安全性
            $param['password'] = password_hash($password,PASSWORD_DEFAULT);

            //允许指定字段并保存用户信息到数据库
            $result = $member->allowField(['password'])->save($param);
            (new CodeService())->deleteCode($param['phone']);
            return $result;
        }catch (\Exception $e){
            throw new ParameterException($e->getMessage());
        }

    }

    public function read($id)
    {

        $result = $this->member->with('vip')->find($id)->hidden(['password']);
        if(!empty($result)){
            $result = $result->toArray();
            $result['level'] = empty($result['pid'])?"level1":"level2";
            $result['level_txt'] = empty($result['pid'])?"一级总代理":"代理";
            $sysIntro = (new SysIntroService())->getByMarkOne($result['level']);

            $result['level_content'] = $sysIntro->content??'';

            $taskOrderService = new TaskOrderService;

            $self = $taskOrderService->countsByMemberId($id);
            $level = $taskOrderService->countsByPid($id);
            $result['count_total_money'] = sprintf("%.2f",($self??0)+($level??0));
        }
        return $result;
    }



    /**
     * 获取会员VIP ID
     *
     * 此方法首先尝试从请求中获取会员ID，如果未找到，则返回0
     * 接着，它会检查是否有缓存的会员VIP ID，如果有则直接返回
     * 如果没有缓存，它会查询数据库以获取VIP ID，并在获取后进行缓存
     *
     * @return mixed|null 返回会员的VIP ID，如果没有找到或关联，则可能返回null
     */
    public function getMemberVipId()
    {
        $uid = request()->_mid;
        if(empty($uid)) return 0;
        $memberVipId = self::getTempMemberVipId($uid);
        if(!is_null($memberVipId)) return $memberVipId;
        $memberVipId = $this->member->where('id',$uid)->value('vip_id');
        self::setTempMemberVipId($uid,$memberVipId);
        return $memberVipId;
    }

    /**
     * 获取临时存储的会员VIP ID
     *
     * 此方法用于从静态变量$memberVipId中获取会员的VIP ID
     * 如果给定UID的会员VIP ID不存在于该静态变量中，则返回null
     *
     * @param mixed $uid 会员唯一标识符
     * @return mixed|null 返回缓存的会员VIP ID，如果未找到则返回null
     */
    public static function getTempMemberVipId($uid)
    {
        return array_key_exists($uid, self::$memberVipId) ? self::$memberVipId[$uid] : null;
    }

    /**
     * 设置临时存储的会员VIP ID
     *
     * 此方法将给定UID的会员VIP ID存储在静态变量$memberVipId中
     * 这是为了在同一个请求中避免重复查询数据库
     *
     * @param mixed $key 会员唯一标识符
     * @param mixed $value 会员的VIP ID
     */
    public static function setTempMemberVipId($key, $value)
    {
        self::$memberVipId[$key] = $value;
    }

    /**
     * 获取会员VIP折扣
     *
     * 此方法首先检查VIP ID是否为空，如果为空，则返回0
     * 然后，它会尝试从缓存中获取会员的VIP折扣，如果找到则直接返回
     * 如果没有缓存，它会查询数据库以获取折扣信息，并在获取后进行缓存
     *
     * @param mixed $vipId 会员的VIP ID
     * @return mixed|null 返回会员的VIP折扣，如果没有找到或关联，则可能返回null
     */
    public function getMemberVipDiscount($vipId)
    {
        if(empty($vipId)) return 0;
        $memberVipDiscount = self::getTempMemberVipDiscount($vipId);
        if(!is_null($memberVipDiscount)) return $memberVipDiscount;
        $discount = (new MemberVip)->where(['id'=>$vipId])->value('discount');
        self::setTempMemberVipDiscount($vipId,$discount);
        return $discount;
    }

    /**
     * 设置临时存储的会员VIP折扣
     *
     * 此方法将给定VIP ID的折扣信息存储在静态变量$tempDiscountData中
     * 这是为了在同一个请求中避免重复查询数据库
     *
     * @param mixed $key 会员的VIP ID
     * @param mixed $value 会员的折扣信息
     */
    public static function setTempMemberVipDiscount($key, $value)
    {
        self::$tempDiscountData[$key] = $value;
    }

    /**
     * 获取临时存储的会员VIP折扣
     *
     * 此方法用于从静态变量$tempDiscountData中获取会员的VIP折扣
     * 如果给定VIP ID的折扣信息不存在于该静态变量中，则返回null
     *
     * @param mixed $key 会员的VIP ID
     * @return mixed|null 返回缓存的会员VIP折扣，如果未找到则返回null
     */
    public static function getTempMemberVipDiscount($key)
    {
        return array_key_exists($key, self::$tempDiscountData) ? self::$tempDiscountData[$key] : null;
    }

    public function getReference()
    {
        $member = $this->member->find(request()->_mid);
        if(empty($member->pid)) return '';

        $reference = $this->member->where('id',$member->pid)->find();
        return $reference;

    }

    public function getVipInfo()
    {

        return (new MemberVipService())->detail($this->getMemberVipId());
    }

    public function setPercentage($param)
    {

        $proportion = $param['proportion']??0;
        if(!is_numeric($proportion)) throw new ParameterException('比例格式必须是正整数');
        if($proportion<0) throw new ParameterException('比例不能小于0');

        if($proportion>50) throw new ParameterException('比例不能大于百分之50');

        return $this->member->where('id',request()->_mid)->update(['percentage'=>$proportion]);
    }

    public function setProportion($param)
    {
        $proportion = $param['proportion']??0;

        //验证是否为正整数
        if(!is_numeric($proportion)) throw new ParameterException('比例格式必须是正整数');

        if($proportion<0) throw new ParameterException('比例不能小于0');

        if($proportion>50) throw new ParameterException('比例不能大于百分之50');

        $percentage = $this->percentage->where(['member_id'=>request()->_mid,'task_id'=>$param['id']])->find();

        if(!empty($percentage)){
            $percentage->proportion=$proportion;
            return $percentage->save();
        }else{
            return $this->percentage->create([
                'task_id'=>$param['id'],
                'proportion'=>$proportion,
                'member_id'=>request()->_mid
            ]);
        }
    }

    public function getAllTeam()
    {

        //获取一级团队
        $team = $this->member->withCount(['countsTadyTask','countsAllTadyTask'])->where('pid',request()->_mid)->select();

        //获取二级团队
        $team2 = $this->member->withCount(['countsTadyTask','countsAllTadyTask'])->where('pid','in',$team->column('id'))->select();

        return ['team'=>$team,'team2'=>$team2];
    }

    public function children($id)
    {
        return $this->member->with(['vip','manager'])->where('pid',$id)->select()->hidden(['password','id'])->append(['sex_text','status_text']);
    }
}
